<template>
  <div class="dom-carousel" ref="root" :class="`dom-carousel-${direction}`"
      @mouseenter="endCarouselInterval" @mouseleave="startCarouselInterval">
    <slot></slot>
  </div>
</template>

<script>
import {defineComponent} from 'vue';

/**
 * 相较于修改DOM轮播方式的缺点：
 *  - 鼠标移入移出事件的开始暂停轮播不能被外部控制
 *  - carousel事件参数的当前组索引计算准确性待验证
 */
export default defineComponent({
  name: 'ScrollCarousel',
  /**
   * 修改Scroll的轮播方式需要关注手动移动滚动条的相关问题，carousel事件的参数永远为轮播到的组索引，索引编号从0开始
   */
  emits: ['carousel'],
  props: {
    direction: {
      type: String,
      default: 'y',
      validator(value) {
        return ['x', 'y'].includes(value);
      }
    },
    gapX: {
      type: Number,
      default: 12
    },
    gapY: {
      type: Number,
      default: 12
    },
    play: {
      type: Boolean,
      default: true,
    },
    /**
     * 不能太快，>=2000
     */
    cycleMs: {
      type: Number,
      default: 4000,
      validator(value) {
        return value >= 2000;
      }
    },
    /**
     * 必须为首行或首列元素整数倍，首行或首列与轮播方向相关，同时注意每个轮播项的宽高保持一致
     */
    numInGroup: {
      type: Number,
      required: true,
      validator(value) {
        return value > 0;
      }
    },
  },
  data() {
    return {
      carouselInterval: null,
      currentGroupIndex: 0,
      scrollPosition: 0,
    };
  },
  computed: {
    scrollStep() {
      const el = this.$refs.root;

      // 水平方向元素个数
      const containerWidth = el.offsetWidth;
      const itemWidth = el.firstElementChild.offsetWidth;
      const elCountX = Math.round(containerWidth / (itemWidth + this.gapX));

      // 垂直方向元素个数
      const containerHeight = el.offsetHeight;
      const itemHeight = el.firstElementChild.offsetHeight;
      const elCountY = Math.round(containerHeight / (itemHeight + this.gapY));

      if (this.direction === 'x') {
        const countX = Math.ceil(this.numInGroup / elCountY);
        return (itemWidth + this.gapX) * countX;
      }

      if (this.direction === 'y') {
        const countY = Math.ceil(this.numInGroup / elCountX);
        return (itemHeight + this.gapY) * countY;
      }

      return 0;
    }
  },
  watch: {
    play: {
      immediate: true,
      handler(newVal) {
        if (newVal) {
          this.$nextTick(() => {
            this.startCarouselInterval();
          });
        } else {
          this.endCarouselInterval();
        }
      }
    }
  },
  mounted() {
    const el = this.$refs.root;

    // 为每组的第一个元素添加类名
    Array.from(el.children).forEach((item, index) => {
      if (index !== 0 && index % this.numInGroup !== 0) return;

      item.setAttribute('class', item.className + ' carouse-item-start');
    });

    // 监听滚动条移动事件
    el.onscrollend = this.handleScrollEnd;
  },
  beforeUnmount() {
    this.endCarouselInterval();
  },
  methods: {
    startCarouselInterval() {
      if (this.carouselInterval) return;

      // 控制轮播时间
      let cycleMs = this.cycleMs;
      if (cycleMs < 2000) {
        cycleMs = 2000;
      }

      // 轮播组内元素数量默认为1
      let numInGroup = this.numInGroup;
      if (numInGroup <= 0) {
        numInGroup = 1;
      }

      const el = this.$refs.root;
      this.carouselInterval = setInterval(() => {
        // 当前组索引更新，索引从0开始编号
        this.currentGroupIndex++;
        const groupTotal = Math.ceil(Array.from(el.children).length / this.numInGroup);
        if (this.currentGroupIndex >= groupTotal) {
          this.currentGroupIndex = 0;
        }
        this.$emit('carousel', this.currentGroupIndex);

        // 移动滚动条控制轮播位置
        this.scrollPosition += this.scrollStep;
        if (this.currentGroupIndex === 0) {
          this.scrollPosition = 0;
        }
        if (this.direction === 'x') {
          el.scrollTo({
            left: this.scrollPosition,
            behavior: 'smooth',
          });
        }
        if (this.direction === 'y') {
          el.scrollTo({
            top: this.scrollPosition,
            behavior: 'smooth',
          });
        }
      }, cycleMs);
    },
    endCarouselInterval() {
      if (!this.carouselInterval) return;

      clearInterval(this.carouselInterval);
      this.carouselInterval = null;
    },
    handleScrollEnd(event) {
      // 手动移动滚动条时轮播暂停
      if (this.carouselInterval !== null) return;

      let actualDistance = 0;
      if (this.direction === 'x') {
        actualDistance = event.target.scrollLeft;
      }
      if (this.direction === 'y') {
        actualDistance = event.target.scrollTop;
      }

      // 获取当前组索引，防止最后一组不满导致无法到达的情况
      const el = this.$refs.root;
      const actualIndex = actualDistance / this.scrollStep;
      let groupIndex = Math.round(actualIndex);
      const groupTotal = Math.ceil(Array.from(el.children).length / this.numInGroup);
      const integerDistance = groupIndex * this.scrollStep;
      const resultDistance = Math.abs(actualDistance - integerDistance) <= 5 ? integerDistance : actualDistance;
      if (groupIndex !== Math.ceil(actualIndex) && !Number.isInteger(resultDistance)) {
        // 适用于实际（元素+间隙）不等于整数的情况
        groupIndex += 1;
      } else if (Number.isInteger(actualIndex) && groupTotal === groupIndex + 2 && actualDistance % this.scrollStep !== 0) {
        // 倒数第二组 && 实际移动距离不满步长 === 适用于实际（元素+间隙）等于整数的情况
        groupIndex += 1;
      }

      // 滚动条未发生改变则返回
      if (this.currentGroupIndex === groupIndex) return;

      // 更新滚动条位置
      this.scrollPosition = groupIndex * this.scrollStep;

      // 移动当前轮播索引
      this.currentGroupIndex = groupIndex;
      this.$emit('carousel', this.currentGroupIndex);
    }
  }
});
</script>

<style scoped>
.dom-carousel {
  @apply h-full w-full;
}

.dom-carousel-x {
  @apply flex flex-col flex-wrap gap-x-3 gap-y-3 overflow-x-auto;
  scroll-snap-type: x mandatory;

  &::-webkit-scrollbar { /* 滚动条整体样式 */
    height: 6px;
    background-color: transparent;
  }

  &::-webkit-scrollbar-thumb { /* 滚动条里面小方块 */
    height: 6px;
    background: #7D7E7D;
    border-radius: 6px;
  }
}

.dom-carousel-y {
  @apply flex flex-wrap gap-x-3 gap-y-3 overflow-y-auto;
  scroll-snap-type: y mandatory;

  &::-webkit-scrollbar { /* 滚动条整体样式 */
    width: 4px;
    background-color: transparent;
  }

  &::-webkit-scrollbar-thumb { /* 滚动条里面小方块 */
    width: 4px;
    background: #7D7E7D;
    border-radius: 2px;
  }
}
</style>